﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using HappyBa.Core.Interfaces;
using Mnk.Library.WpfControls.Tools;
using ServiceStack.Text;

namespace HappyBa.Core.Data
{
    class TargetProccessFacade : ITargetProccessFacade
    {
        private const string TimeEntityFields = "spent,user[Id,FirstName,LastName,email],date,assignable[id]]";
        private const string CommentEntityFields = "Description,CreateDate,General[Id],Owner[id,FirstName,LastName,Email]";
        private const string AssignableFields = "Assignable[Id],Role[id],GeneralUser[Id,FirstName,LastName,Email]";
        private const string BaseFields = "id,name";
        private const string EntityFields = BaseFields + ",Description,StartDate,EndDate,CreateDate,ModifyDate,LastCommentDate,EntityType[id],Owner[Id,FirstName,LastName,Email],CustomFields";
        private const string UserStoryEntityFields = EntityFields + ",Effort,EffortCompleted,EffortToDo,TimeSpent,TimeRemain,PlannedStartDate,PlannedEndDate,Priority[id,name],EntityState[id,name],Project[id],Release[id],Feature[id],Iteration[id]";
        private const string EstimatableEntityFields = UserStoryEntityFields + ",UserStory[id]";
        private const string AssignableEntityFields = EntityFields + ",Assignable[Id],EntityState[name],Project[id]";
        private const string UserFields = "id,email,FirstName,LastName";
        private const int MaxCount = short.MaxValue;

        private static string BuildSearchString(IEnumerable<ProjectInfo> projects)
        {
            var sb = new StringBuilder();
            foreach (var info in projects.GroupBy(x => x.Type))
            {
                var el = string.Format("{0}.Id in ({1})", info.Key, string.Join(",", info.Select(x => x.Key)));
                if (sb.Length > 0) sb.Append(" or ");
                sb.Append(el);
            }
            return string.Format("({0})", sb);
        }

        public IList<Project> GetProjects(Connection connection, IEnumerable<ProjectInfo> projects)
        {
            var result = new List<Project>();
            foreach (var info in projects.GroupBy(x => x.Type))
            {
                using (var cl = CreateClient(connection))
                {
                    var query = string.Format("{0}s?format=json&skip=0&take={1}&include=[{2}]&where=Id in ({3})", info.Key, MaxCount, EntityFields, string.Join(",", info.Select(x => x.Key)));
                    var str = cl.DownloadString(BuildUrl(connection, query));
                    var items = JsonSerializer.DeserializeFromString<BaseCollection<Project>>(str).Items;
                    foreach (var item in items)
                    {
                        item.Type = info.Key;
                    }
                    result.AddRange(items);
                }
            }
            return result;
        }

        public IEnumerable<Role> GetRoles(Connection connection)
        {
            using (var cl = CreateClient(connection))
            {
                var query = string.Format("Roles?format=json&skip=0&take={0}&include=[{1}]", MaxCount, BaseFields);
                var str = cl.DownloadString(BuildUrl(connection, query));
                return JsonSerializer.DeserializeFromString<BaseCollection<Role>>(str).Items;
            }
        }

        public IEnumerable<EntityType> GetEntityTypes(Connection connection)
        {
            using (var cl = CreateClient(connection))
            {
                var query = string.Format("EntityTypes?format=json&skip=0&take={0}&include=[{1}]", MaxCount, BaseFields);
                var str = cl.DownloadString(BuildUrl(connection, query));
                return JsonSerializer.DeserializeFromString<BaseCollection<EntityType>>(str).Items;
            }
        }

        public IList<Bug> GetAllBugs(Connection connection, IEnumerable<ProjectInfo> projects)
        {
            using (var cl = CreateClient(connection))
            {
                var query = string.Format("Bugs?format=json&skip=0&take={0}&include=[{1}]&where={2}", MaxCount, EstimatableEntityFields, BuildSearchString(projects));
                var str = cl.DownloadString(BuildUrl(connection, query));
                return JsonSerializer.DeserializeFromString<BaseCollection<Bug>>(str).Items;
            }
        }

        public IList<UserStory> GetAllUserStories(Connection connection, IEnumerable<ProjectInfo> projects)
        {
            using (var cl = CreateClient(connection))
            {
                var query = string.Format("UserStories?format=json&skip=0&take={0}&include=[{1}]&where={2}", MaxCount, UserStoryEntityFields, BuildSearchString(projects));
                var str = cl.DownloadString(BuildUrl(connection, query));
                return JsonSerializer.DeserializeFromString<BaseCollection<UserStory>>(str).Items;
            }
        }

        public IList<Task> GetAllTasks(Connection connection, IEnumerable<int> userStories)
        {
            using (var cl = CreateClient(connection))
            {
                var query = string.Format("Tasks?format=json&skip=0&take={0}&include=[{1}]&where=UserStory.Id in ({2})", MaxCount, EstimatableEntityFields.Replace(",Feature[id]", ""), string.Join(",", userStories));
                var str = cl.DownloadString(BuildUrl(connection, query));
                return JsonSerializer.DeserializeFromString<BaseCollection<Task>>(str).Items;
            }
        }

        public IList<Assignment> GetAssigments(Connection connection, IEnumerable<int> ids)
        {
            return Chunk(ids, 200).AsParallel().SelectMany(items =>
            {
                using (var cl = CreateClient(connection))
                {
                    var query = string.Format("Assignments?format=json&skip=0&take={0}&include=[{1}]&where=Assignable.Id in ({2})", MaxCount, AssignableFields, string.Join(",", items));
                    var str = cl.DownloadString(BuildUrl(connection, query));
                    return JsonSerializer.DeserializeFromString<BaseCollection<Assignment>>(str).Items;
                }
            })
            .ToArray();
        }

        public IList<Impediment> GetImpediments(Connection connection, IEnumerable<int> ids)
        {
            return Chunk(ids, 200).AsParallel().SelectMany(items =>
            {
                using (var cl = CreateClient(connection))
                {
                    var query = string.Format("Impediments?format=json&skip=0&take={0}&include=[{1}]&where=Assignable.Id in ({2})", MaxCount, AssignableEntityFields, string.Join(",", items));
                    var str = cl.DownloadString(BuildUrl(connection, query));
                    return JsonSerializer.DeserializeFromString<BaseCollection<Impediment>>(str).Items;
                }
            })
            .ToArray();
        }

        public IList<Comment> GetComments(Connection connection, IEnumerable<int> ids)
        {
            return Chunk(ids, 200).AsParallel().SelectMany(items =>
            {
                using (var cl = CreateClient(connection))
                {
                    var query = string.Format("Comments?format=json&skip=0&take={0}&include=[{1}]&where=General.Id in ({2})", MaxCount, CommentEntityFields, string.Join(",", items));
                    var str = cl.DownloadString(BuildUrl(connection, query));
                    return JsonSerializer.DeserializeFromString<BaseCollection<Comment>>(str).Items;
                }
            })
            .ToArray();
        }

        public IList<Time> GetTimes(Connection connection, IEnumerable<int> ids)
        {
            return Chunk(ids, 100).AsParallel().SelectMany(items =>
            {
                using (var cl = CreateClient(connection))
                {
                    var query = string.Format("Times?format=json&skip=0&take={0}&include=[{1}]&where=(Assignable.Id in ({2}))and(date gte'{3}')and(date lte'{4}') ",
                        MaxCount, TimeEntityFields, string.Join(",", items),
                        FormatDate(DateTime.UtcNow.AddDays(-14)),
                        FormatDate(DateTime.UtcNow)
                        );
                    var str = cl.DownloadString(BuildUrl(connection, query));
                    return JsonSerializer.DeserializeFromString<BaseCollection<Time>>(str).Items;
                }
            })
            .ToArray();
        }

        public IList<Time> GetTimes(Connection connection, DateTime from, DateTime to, IEnumerable<string> users)
        {
            return Chunk(users, 10).AsParallel().SelectMany(items =>
            {
                using (var cl = CreateClient(connection))
                {
                    var query = string.Format("Times?format=json&skip=0&take={0}&include=[{1}]&where=(user.LastName in ({2}))and(date gte'{3}')and(date lte'{4}')",
                        MaxCount, TimeEntityFields, 
                        string.Join(",", items.Select(x=>"'" + x.Split(' ').Last() + "'")),
                        FormatDate(from),
                        FormatDate(to)
                        );
                    var str = cl.DownloadString(BuildUrl(connection, query));
                    return JsonSerializer.DeserializeFromString<BaseCollection<Time>>(str).Items;
                }
            })
            .ToArray();
        }

        public IList<EntityBase> GetAssignables(Connection connection, IEnumerable<int> ids)
        {
            return Chunk(ids, 100).AsParallel().SelectMany(items =>
            {
                using (var cl = CreateClient(connection))
                {
                    var query = string.Format("Assignables?format=json&skip=0&take={0}&include=[{1}]&where=(Id in ({2}))",
                        MaxCount, EntityFields,
                        string.Join(",", items)
                        );
                    var str = cl.DownloadString(BuildUrl(connection, query));
                    return JsonSerializer.DeserializeFromString<BaseCollection<EntityBase>>(str).Items;
                }
            })
            .ToArray();
        }

        public IList<User> GetAllUsers(Connection connection)
        {
            using (var cl = CreateClient(connection))
            {
                var query = string.Format("Users?format=json&skip=0&take={0}&include=[{1}]",
                    MaxCount, UserFields
                    );
                var str = cl.DownloadString(BuildUrl(connection, query));
                return JsonSerializer.DeserializeFromString<BaseCollection<User>>(str).Items;
            }

        }

        public T CreateOrUpdate<T>(Connection connection, string type, T detail)
        {
            using (var cl = CreateClient(connection))
            {
                string query = string.Format("{0}?format=json", type);
                var data = cl.UploadString(
                    BuildUrl(connection, query),
                    JsonSerializer.SerializeToString(detail)
                    );
                return JsonSerializer.DeserializeFromString<T>(data);
            }
        }

        private static string FormatDate(DateTime date)
        {
            return date.ToString("yyyy-MM-dd");
        }

        private static IEnumerable<IEnumerable<T>> Chunk<T>(IEnumerable<T> source, int chunksize)
        {
            return source
                    .Select((x, i) => new { Index = i, Value = x })
                    .GroupBy(x => x.Index / chunksize)
                    .Select(x => x.Select(v => v.Value).ToList())
                    .ToList();
        }

        private static WebClient CreateClient(Connection connection)
        {
            var cl = new WebClientWithTimeout(20 * 60) { Credentials = new NetworkCredential(connection.LoginName, connection.Password.DecryptPassword()) };
            cl.Headers.Add("Content-Type", "application/json; charset=utf-8");
            cl.Headers.Add("Accept-Encoding", "gzip, deflate");
            cl.Headers.Add("Accept-Language", "en-US,en;q=0.5");
            cl.Encoding = Encoding.UTF8;
            return cl;
        }

        private static Uri BuildUrl(Connection connection, string relativeUri)
        {
            var url = connection.TargetProcessUrl;
            if (!string.IsNullOrEmpty(url) && url[url.Length - 1] != '/')
            {
                url += "/";
            }
            return new Uri(new Uri(url + "TargetProcess2/api/v1/"), relativeUri);
        }
    }
}